#include "fiber.h"
#include "config.h"
#include "macro.h"
#include "log.h"
#include "scheduler.h"
#include <atomic>

namespace sylar {

  static Logger::ptr g_logger = SYLAR_LOG_NAME("system");

  static std::atomic<uint64_t> s_fiber_id {0};
  static std::atomic<uint64_t> s_fiber_count {0};

  /// t_fiber: 当前正在执行的协橙
  /// t_threadFiber: 当前线程的主协橙
  static thread_local Fiber* t_fiber = nullptr;
  static thread_local Fiber::ptr t_threadFiber = nullptr;

  static ConfigVar<uint32_t>::ptr g_fiber_stack_size =
          Config::LookUp<uint32_t>("fiber.stack_size", 128 * 1024, "fiber stack size");

  class MallocStackAllocator {
  public:
    static void* Alloc(size_t size) {
      return malloc(size);
    }

    static void Dealloc(void* vp, size_t size) {
      return free(vp);
    }
  };

  using StackAllocator = MallocStackAllocator;

  uint64_t Fiber::GetFiberId() {
    if(t_fiber) {
      return t_fiber->getId();
    }
    return 0;
  }

  Fiber::Fiber() {
    m_state = EXEC;
    SetThis(this);

    if(getcontext(&m_ctx)) {
      SYLAR_ASSERT2(false, "getcontext");
    }
    ++s_fiber_count;

  }
  /**
   * @details[makecontext]:  makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext)。
   *       然后给该上下文指定一个栈空间ucp->stack，设置后继的上下文ucp->uc_link.
   *       当上下文通过setcontext或者swapcontext激活后，执行func函数，argc为func的参数个数，
   *       后面是func的参数序列。当func执行返回后，继承的上下文被激活，如果继承上下文为NULL时，线程退出。
   * */
  Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool use_caller)
          :m_id(++s_fiber_id)
          ,m_cb(cb) {
    ++s_fiber_count;
    m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();

    m_stack = StackAllocator::Alloc(m_stacksize);
    if(getcontext(&m_ctx)) {
      SYLAR_ASSERT2(false, "getcontext");
    }
    m_ctx.uc_link = nullptr;
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;
    if(!use_caller) {
      makecontext(&m_ctx, &Fiber::MainFunc, 0);
    } else {
      makecontext(&m_ctx, &Fiber::CallerMainFunc, 0);
    }
  }

  Fiber::~Fiber() {
    --s_fiber_count;
    if(m_stack) {
      SYLAR_ASSERT(m_state == TERM
                   || m_state == EXCEPT
                   || m_state == INIT);

      StackAllocator::Dealloc(m_stack, m_stacksize);
    } else {
      SYLAR_ASSERT(!m_cb);
      SYLAR_ASSERT(m_state == EXEC);

      Fiber* cur = t_fiber;
      if(cur == this) {
        SetThis(nullptr);
      }
    }
  }

  //重置协程函数，并重置状态
  //INIT，TERM, EXCEPT
  void Fiber::reset(std::function<void()> cb) {
    SYLAR_ASSERT(m_stack);
    SYLAR_ASSERT(m_state == TERM || m_state == EXCEPT || m_state == INIT);
    m_cb = cb;
    if(getcontext(&m_ctx)) {
      SYLAR_ASSERT2(false, "getcontext");
    }

    m_ctx.uc_link = nullptr;
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;

    makecontext(&m_ctx, &Fiber::MainFunc, 0);
    m_state = INIT;
  }
  /// swapcontext(): 将当前线程上下文保存在param1， 切换到param2的上下文
  void Fiber::call() {
    SetThis(this);
    m_state = EXEC;
    if(swapcontext(&t_threadFiber->m_ctx, &m_ctx)) {
      SYLAR_ASSERT2(false, "swapcontext");
    }
  }

  void Fiber::back() {
    SetThis(t_threadFiber.get());
    if(swapcontext(&m_ctx, &t_threadFiber->m_ctx)) {
      SYLAR_ASSERT2(false, "swapcontext");
    }
  }
  // 函数调用时候，先对参数压栈，再压入返回地址，再执行被调函数体指令。
  /**
   * @brief：被调度器调用，负责在调度协橙和任务协橙之间转换
   * |---------------------------------------------------------------|
   * |  t_thread_fiber         t_scheduler_fiber           t_fiber   |
   * |                call()                     swapIn()            |
   * |               --------->                 --------->           |
   * |                back()                     swapOut()           |
   * |               <--------                  --------->           |
   * */
  void Fiber::swapIn() {
    SetThis(this);
    SYLAR_ASSERT(m_state != EXEC);
    m_state = EXEC;
    // printf("%p  %p\n", &Scheduler::GetMainFiber()->m_ctx, &m_ctx);
    if(swapcontext(&Scheduler::GetMainFiber()->m_ctx, &m_ctx)) {
      SYLAR_ASSERT2(false, "swapcontext");
    }
  }

  void Fiber::swapOut() {
    SetThis(Scheduler::GetMainFiber());
    if(swapcontext(&m_ctx, &Scheduler::GetMainFiber()->m_ctx)) {
      SYLAR_ASSERT2(false, "swapcontext");
    }
  }

//设置当前协程
  void Fiber::SetThis(Fiber* f) {
    t_fiber = f;
  }

  /**
   * @details: 协橙使用前需要显式调用GetThis() 来为当前协橙初始化一个主协橙
   * @return: 当前协橙
   * */
  Fiber::ptr Fiber::GetThis() {
    if(t_fiber) {
      return t_fiber->shared_from_this();
    }
    Fiber::ptr main_fiber(new Fiber);
    SYLAR_ASSERT(t_fiber == main_fiber.get());
    t_threadFiber = main_fiber;
    return t_fiber->shared_from_this();
  }

//协程切换到后台，并且设置为Ready状态
  void Fiber::YieldToReady() {
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur->m_state == EXEC);
    cur->m_state = READY;
    cur->swapOut();
  }

//协程切换到后台，并且设置为Hold状态
  void Fiber::YieldToHold() {
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur->m_state == EXEC);
    //cur->m_state = HOLD;
    cur->swapOut();
  }

//总协程数
  uint64_t Fiber::TotalFibers() {
    return s_fiber_count;
  }
  /**
   * @brief：使用mainfunc包装回调函数，使得他能在函数执行结束后自动调用yield退回到主协橙
   * */
  void Fiber::MainFunc() {
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur);
    try {
      cur->m_cb();
      cur->m_cb = nullptr;
      cur->m_state = TERM;
    } catch (std::exception& ex) {
      cur->m_state = EXCEPT;
      SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << ex.what()
                                << " fiber_id=" << cur->getId()
                                << std::endl
                                << sylar::BackTraceToString();
    } catch (...) {
      cur->m_state = EXCEPT;
      SYLAR_LOG_ERROR(g_logger) << "Fiber Except"
                                << " fiber_id=" << cur->getId()
                                << std::endl
                                << sylar::BackTraceToString();
    }
    /// 担心协橙退出后智能指针没有被销毁，导致对象无法被销毁
    auto raw_ptr = cur.get();
    cur.reset();
    raw_ptr->swapOut();

    SYLAR_ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId()));
  }

  void Fiber::CallerMainFunc() {
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur);
    try {
      cur->m_cb();
      cur->m_cb = nullptr;
      cur->m_state = TERM;
    } catch (std::exception& ex) {
      cur->m_state = EXCEPT;
      SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << ex.what()
                                << " fiber_id=" << cur->getId()
                                << std::endl
                                << sylar::BackTraceToString();
    } catch (...) {
      cur->m_state = EXCEPT;
      SYLAR_LOG_ERROR(g_logger) << "Fiber Except"
                                << " fiber_id=" << cur->getId()
                                << std::endl
                                << sylar::BackTraceToString();
    }

    auto raw_ptr = cur.get();
    cur.reset();
    raw_ptr->back();
    SYLAR_ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId()));
  }

}